package dao

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/go-xorm/xorm"
	"github.com/mitchellh/mapstructure"
	"github.com/olivere/elastic/v7"

	"im/common"
	"im/dao/es"
	"im/errcode"
	"im/model"
	"im/requests"
	"im/service/orm"
)

const IndexNameOfPosts = "posts"

const (
	RuleKeyDistance                       = "distance"
	RuleKeyAging                          = "aging"
	RuleKeyRelations                      = "relations"
	RuleKeyRelationsInterests             = "relations.interests"
	RuleKeyQuality                        = "quality"
	RuleKeyQualityInteract                = "quality.interact"
	RuleKeyInteract                       = "interact"
	RuleKeyQualityInteractShares          = "quality.interact.shares"
	RuleKeyQualityInteractLikes           = "quality.interact.likes"
	RuleKeyQualityInteractComments        = "quality.interact.comments"
	RuleKeyQualityInteractPrivateMessages = "quality.interact.private_messages"
	RuleKeyShares                         = "shares"
	RuleKeyLikes                          = "likes"
	RuleKeyComments                       = "comments"
	RuleKeyPrivateMessage                 = "private_messages"
	RuleKeyQualityView                    = "quality.view"
	RuleKeyQualityViewOpen                = "quality.view.opens"
	RuleKeyQualityViewPartReads           = "quality.view.part_reads"
	RuleKeyQualityViewAllReads            = "quality.view.all_reads"
)

type Posts interface {
	GetByIds(ids ...int64) (map[int64]model.Posts, error)
	GetById(id int64) (model.Posts, error)
	Insert(userId int64, mm model.PostsModifiable) (model.Posts, error)
	Hide(userId int64, id int64) error
	Belong(userId int64, id int64) bool
	Update(id int64, mm model.PostsModifiable) error
	AddStat(id int64, statType model.PostStatType) error
	SubStat(id int64, statType model.PostStatType) error
	List(userId int64, pagination requests.Pagination) ([]model.Posts, error)
	// userId暂时没用到
	ListRecommend(user model.Users, pagination requests.GetRecommendPosts, groupIds ...interface{}) ([]model.Posts, error)
	// add group for all ids
	UpdateGroup(userId int64, groupIds ...int64) error
	GetByGroupId(groupId int64, location string, pagination requests.Pagination) ([]PostsWithDistance, error)
	GetLikedPostIdsBatch(userId int64, postIds ...int64) (map[int64]interface{}, error)
}

func NewPostsWithEs() Posts {
	return postsWithEs{dbHandle: NewPosts()}
}
func NewPosts() Posts {
	return posts{db: orm.GetDB()}
}

type postsWithEs struct {
	dbHandle Posts
}

func (p postsWithEs) GetLikedPostIdsBatch(userId int64, postIds ...int64) (map[int64]interface{}, error) {
	return p.dbHandle.GetLikedPostIdsBatch(userId, postIds...)
}

type PostsWithDistance struct {
	model.Posts
	Distance string `json:"distance"`
}

func (p postsWithEs) GetByGroupId(groupId int64, location string, pagination requests.Pagination) ([]PostsWithDistance, error) {
	var ms []PostsWithDistance
	var err error
	client, err := es.New().GetClient()
	if err != nil {
		return ms, err
	}
	geoPointArr := strings.Split(location, ",")
	if len(geoPointArr) != 2 {
		return ms, errcode.NewError(errcode.RequestParamInvalid, errors.New(location))
	}
	searchSource := elastic.NewSearchSource().ScriptField(elastic.NewScriptField("distance", elastic.NewScript(`
	if(doc['location'].size()==0)return 100000;
         doc['location'].planeDistance(params.lat,params.lon)
	`).Param("lat", common.TransferFloat(geoPointArr[0])).Param("lon", common.TransferFloat(geoPointArr[1])))).Query(elastic.NewTermQuery("from_group_id", groupId)).FetchSource(true).Size(pagination.Limit).From(pagination.GetOffset())
	resp, err := client.Search().Index(es.WithPrefix(IndexNameOfPosts)).SearchSource(searchSource).
		Do(context.Background())
	if err != nil {
		return ms, errcode.NewError(errcode.DaoFailed, err)
	}
	for _, hit := range resp.Hits.Hits {
		var pp EsPosts
		err = json.Unmarshal(hit.Source, &pp)
		if err != nil {
			errcode.NewError(errcode.EsError, err)
		}
		distance := hit.Fields["distance"].([]interface{})[0].(float64)
		post := pp.Posts
		post.Created = common.Time(pp.Created)
		post.Updated = common.Time(pp.Updated)
		ms = append(ms, PostsWithDistance{
			Posts:    post,
			Distance: p.formatDistance(distance),
		})
	}
	return ms, nil
}
func (p postsWithEs) formatDistance(f float64) string {
	if f > 1000 {
		return fmt.Sprintf("%dkm", int(f/1000))
	}
	return fmt.Sprintf("%dm", int(f))
}
func (p postsWithEs) Belong(userId int64, id int64) bool {
	return p.dbHandle.Belong(userId, id)
}

func (p postsWithEs) UpdateGroup(userId int64, groupIds ...int64) error {
	err := p.dbHandle.UpdateGroup(userId, groupIds...)
	if err != nil {
		return err
	}
	return es.New().UpdateByField(IndexNameOfPosts, elastic.NewScript(`ctx._source.related_groups =params.related_groups`).Param("related_groups", groupIds), "user_id", userId)
}

func (p postsWithEs) GetByIds(ids ...int64) (map[int64]model.Posts, error) {
	// todo
	panic("implement me")
}

func (p postsWithEs) GetById(id int64) (model.Posts, error) {
	return p.dbHandle.GetById(id)
}

type EsPosts struct {
	model.Posts
	model.TimeModelOfEs
	model.RecommendMeasuresOfEs
}

func (p postsWithEs) Insert(userId int64, mm model.PostsModifiable) (model.Posts, error) {
	m, err := p.dbHandle.Insert(userId, mm)
	if err != nil {
		return m, err
	}
	esM := EsPosts{
		Posts: m,
		TimeModelOfEs: model.TimeModelOfEs{
			time.Time(m.TimeModel.Created),
			time.Time(m.TimeModel.Updated),
		},
		RecommendMeasuresOfEs: model.RecommendMeasuresOfEs{
			Placeholder1: model.NewRecommendMeasure(m.Placeholder1),
			Placeholder2: model.NewRecommendMeasure(m.Placeholder2),
			Placeholder3: model.NewRecommendMeasure(m.Placeholder3),
			Placeholder4: model.NewRecommendMeasure(m.Placeholder4),
			Placeholder5: model.NewRecommendMeasure(m.Placeholder5),
			Placeholder6: model.NewRecommendMeasure(m.Placeholder6),
		},
	}
	return m, es.New().IndexDoc(IndexNameOfPosts, m.Id, esM)
}

func (p postsWithEs) Hide(userId int64, id int64) error {
	err := p.dbHandle.Hide(userId, id)
	if err != nil {
		return err
	}
	return es.New().Delete(IndexNameOfPosts, id)
}

func (p postsWithEs) Update(id int64, mm model.PostsModifiable) error {
	err := p.dbHandle.Update(id, mm)
	if err != nil {
		return err
	}
	var data = map[string]interface{}{}
	if mm.Content != "" {
		data["content"] = mm.Content
	}
	if mm.Title != "" {
		data["title"] = mm.Title
	}
	if mm.CoverImage != "" {
		data["cover_image"] = mm.CoverImage
	}
	if mm.FromGroupId != nil && len(mm.FromGroupId) > 0 {
		data["from_group_id"] = mm.FromGroupId
	}
	return es.New().Update(IndexNameOfPosts, id, data)
}

func (p postsWithEs) AddStat(id int64, statType model.PostStatType) error {
	err := p.dbHandle.AddStat(id, statType)
	if err != nil {
		return err
	}
	return es.New().Increase(IndexNameOfPosts, id, string(statType))
}

func (p postsWithEs) SubStat(id int64, statType model.PostStatType) error {
	err := p.dbHandle.SubStat(id, statType)
	if err != nil {
		return err
	}
	return es.New().Decrease(IndexNameOfPosts, id, string(statType))
}

func (p postsWithEs) List(userId int64, pagination requests.Pagination) (ms []model.Posts, err error) {
	defer func() {
		if ee := recover(); ee != nil {
			err = errcode.NewError(errcode.UnexpectedError, ee)
			return
		}
	}()
	client, err := es.New().GetClient()
	if err != nil {
		return ms, err
	}
	result, err := client.Search().Index(es.WithPrefix(IndexNameOfPosts)).
		Query(elastic.NewTermQuery("user_id", userId)).
		Sort("created", false).
		From(pagination.GetOffset()).Size(pagination.Limit).
		Do(context.Background())
	if err != nil {
		err = errcode.NewError(errcode.EsError, err)
		return
	}
	for _, item := range result.Each(reflect.TypeOf(EsPosts{})) {
		em := item.(EsPosts)
		m := em.Posts
		m.Created = common.Time(em.Created)
		m.Updated = common.Time(em.Updated)
		m.Placeholder1 = em.Placeholder1.String()
		m.Placeholder2 = em.Placeholder2.String()
		m.Placeholder3 = em.Placeholder3.String()
		m.Placeholder4 = em.Placeholder4.String()
		m.Placeholder5 = em.Placeholder5.String()
		m.Placeholder6 = em.Placeholder6.String()
		ms = append(ms, m)
	}
	return
}

type FeedsConfig struct {
	globalConfig      *simplejson.Json
	channelConfigsMap map[string]*simplejson.Json
}

func (f FeedsConfig) GetScale(ruleKey string) float64 {
	ruleConfig := f.globalConfig.Get(ruleKey)
	var scale float64 = 0
	if s, err := ruleConfig.Get("scale").Float64(); err == nil {
		scale = s
	}
	return scale
}
func (f FeedsConfig) GetThresholds(ruleKey string) []Threshold {
	var thresholds []Threshold
	ruleConfig := f.globalConfig.Get(ruleKey)
	if ranges, err := ruleConfig.Get("ranges").MarshalJSON(); err == nil {
		_ = json.Unmarshal(ranges, &thresholds)
	}
	if thresholds == nil {
		thresholds = []Threshold{}
	}
	return thresholds
}
func (f FeedsConfig) GetChannelInteractThresholds(field string) map[string][]Threshold {
	var ths = map[string][]Threshold{}
	for k, v := range f.channelConfigsMap {
		if v == nil {
			continue
		}
		var thresholds []Threshold
		ranges, err := v.Get(field).Get("ranges").MarshalJSON()
		if err != nil {
			continue
		}
		err = json.Unmarshal(ranges, &thresholds)
		if err != nil {
			continue
		}
		ths[k] = thresholds
	}
	return ths
}
func (f FeedsConfig) GetChannelInteractScales(field string) map[string]float64 {
	var scales = map[string]float64{}
	var qualityScale = f.GetScale(RuleKeyQuality)
	for k, v := range f.channelConfigsMap {
		if v == nil {
			continue
		}
		interactScale, err := v.Get(RuleKeyInteract).Get("scale").Float64()
		if err != nil {
			continue
		}
		fieldScale, _ := v.Get(field).Get("scale").Float64()
		scales[k] = calcuScales(interactScale, fieldScale, qualityScale)
	}
	return scales
}

func NewFeedConfig() FeedsConfig {
	globalConfigStr, _ := NewSystemConfigs().GetConfig(model.ConfigOfFeeds)
	globalConfig, _ := simplejson.NewJson([]byte(globalConfigStr))
	channelConfigs, _ := NewChannels().ListConfigs()
	var channelConfigsMap = map[string]*simplejson.Json{}
	for _, row := range channelConfigs {
		channelConfigsMap[row.Name] = row.Feeds
	}
	return FeedsConfig{
		globalConfig:      globalConfig,
		channelConfigsMap: channelConfigsMap,
	}
}

func (p postsWithEs) ListRecommend(user model.Users, param requests.GetRecommendPosts, groupIds ...interface{}) (ms []model.Posts, err error) {
	e := es.New()
	client, err := e.GetClient()
	if err != nil {
		return ms, err
	}
	var (
		feedConfig = NewFeedConfig()
		r          postRules
		latlong          = param.Location
		readCount  int64 = 100
		query      elastic.Query
	)
	switch param.Filter {
	case requests.GetRecommendPostsFilterMineSent:
		query = elastic.NewTermQuery("user_id", user.Id)
	case requests.GetRecommendPostsFilterMineReply:
		var ids []int64
		ids, err = NewReplys().GetReplyedBefore(user.Id)
		if err != nil {
			return
		}
		query = elastic.NewTermsQuery("id", common.ToInterfaceSlice(ids)...)
	default:
		query = elastic.NewTermsQuery("related_groups", groupIds...)
	}
	scoreQuery := elastic.NewFunctionScoreQuery().
		AddScoreFunc(r.Distance(latlong, feedConfig.GetThresholds(RuleKeyDistance), calcuScales(feedConfig.GetScale(RuleKeyDistance)))). // 距离评分
		AddScoreFunc(r.TimeAging(feedConfig.GetThresholds(RuleKeyAging), calcuScales(feedConfig.GetScale(RuleKeyAging)))). // 时间评分

		// 相关度
		AddScoreFunc(r.RelationInterest(readCount, feedConfig.GetThresholds(RuleKeyRelations),
			calcuScales(feedConfig.GetScale(RuleKeyRelations), feedConfig.GetScale(RuleKeyRelationsInterests)))). // 相关度.兴趣评分

		// 质量交互
		AddScoreFunc(r.QualityInteract(RuleKeyShares, feedConfig.GetChannelInteractThresholds(RuleKeyShares), feedConfig.GetChannelInteractScales(RuleKeyShares),
			feedConfig.GetThresholds(RuleKeyQualityInteractShares),
			calcuScales(feedConfig.GetScale(RuleKeyQuality), feedConfig.GetScale(RuleKeyQualityInteract)*feedConfig.GetScale(RuleKeyQualityInteractShares)))).
		AddScoreFunc(r.QualityInteract(RuleKeyLikes, feedConfig.GetChannelInteractThresholds(RuleKeyLikes), feedConfig.GetChannelInteractScales(RuleKeyLikes),
			feedConfig.GetThresholds(RuleKeyQualityInteractLikes),
			calcuScales(feedConfig.GetScale(RuleKeyQuality), feedConfig.GetScale(RuleKeyQualityInteract)*feedConfig.GetScale(RuleKeyQualityInteractShares)))).
		AddScoreFunc(r.QualityInteract(RuleKeyComments, feedConfig.GetChannelInteractThresholds(RuleKeyComments), feedConfig.GetChannelInteractScales(RuleKeyComments),
			feedConfig.GetThresholds(RuleKeyQualityInteractComments),
			calcuScales(feedConfig.GetScale(RuleKeyQuality), feedConfig.GetScale(RuleKeyQualityInteract)*feedConfig.GetScale(RuleKeyQualityInteractComments)))).
		AddScoreFunc(r.QualityInteract(RuleKeyPrivateMessage, feedConfig.GetChannelInteractThresholds(RuleKeyPrivateMessage), feedConfig.GetChannelInteractScales(RuleKeyPrivateMessage),
			feedConfig.GetThresholds(RuleKeyQualityInteractPrivateMessages),
			calcuScales(feedConfig.GetScale(RuleKeyQuality), feedConfig.GetScale(RuleKeyQualityInteract)*feedConfig.GetScale(RuleKeyQualityInteractPrivateMessages)))).

		// 质量.浏览
		AddScoreFunc(r.QualityViewOpen(feedConfig.GetThresholds(RuleKeyQualityViewOpen), // 质量.浏览.打开评分
			calcuScales(feedConfig.GetScale(RuleKeyQuality), feedConfig.GetScale(RuleKeyQualityView)*feedConfig.GetScale(RuleKeyQualityViewOpen)))).
		AddScoreFunc(r.QualityViewPartReads(feedConfig.GetThresholds(RuleKeyQualityViewPartReads), // 质量.浏览.有效浏览评分
			calcuScales(feedConfig.GetScale(RuleKeyQuality), feedConfig.GetScale(RuleKeyQualityView), feedConfig.GetScale(RuleKeyQualityViewPartReads)))).
		AddScoreFunc(r.QualityViewAllReads(feedConfig.GetThresholds(RuleKeyQualityViewAllReads), // 质量.浏览.读完评分
			calcuScales(feedConfig.GetScale(RuleKeyQuality), feedConfig.GetScale(RuleKeyQualityView), feedConfig.GetScale(RuleKeyQualityViewAllReads)))).
		AddScoreFunc(r.HumanScore()).
		BoostMode("sum").ScoreMode("sum")
	// 编码score函数
	result, err := client.Search().Index(es.WithPrefix(IndexNameOfPosts)).
		// todo
		Query(elastic.NewBoolQuery().Must(elastic.NewTermQuery("switch", 1), scoreQuery, query, elastic.NewTermQuery("see_status", model.Post_See_EveryBody))).
		From(param.GetOffset()).Size(param.Limit).
		SortBy(elastic.NewScoreSort().Order(false)).
		Do(context.Background())

	if err != nil {
		err = errcode.NewError(errcode.EsError, err)
		return
	}
	for _, item := range result.Each(reflect.TypeOf(EsPosts{})) {
		em := item.(EsPosts)
		m := em.Posts
		m.Created = common.Time(em.Created)
		m.Updated = common.Time(em.Updated)
		m.Placeholder1 = em.Placeholder1.String()
		m.Placeholder2 = em.Placeholder2.String()
		m.Placeholder3 = em.Placeholder3.String()
		m.Placeholder4 = em.Placeholder4.String()
		m.Placeholder5 = em.Placeholder5.String()
		m.Placeholder6 = em.Placeholder6.String()
		ms = append(ms, m)
	}
	return
}
func calcuScales(scales ...float64) float64 {
	var all float64 = 1
	for _, scale := range scales {
		all = all * scale / 100
	}
	return all
}

// 传入百分比的threshold,然后转化为固定数值的threshold
// 这里拟定 start,end都是超过x%的比例
func (p postsWithEs) getOpenPercentiles(thresholds []Threshold) ([]Threshold, error) {
	var thresholdsNew = []Threshold{}
	e := es.New()
	client, err := e.GetClient()
	if err != nil {
		return thresholdsNew, err
	}
	// 将start进行查询
	var keys = []float64{}
	for _, v := range thresholds {
		keys = append(keys, float64(v.Start))
		if v.End != -1 {
			keys = append(keys, float64(v.End))
		}
	}
	result, err := client.Search(es.WithPrefix(IndexNameOfPosts)).
		Aggregation("open_percs", elastic.NewPercentilesAggregation().
			Field("open").Percentiles(keys...)).Do(context.Background())
	if err != nil {
		err = errcode.NewError(errcode.EsError, err)
		return thresholds, err
	}
	openPercMertics, ok := result.Aggregations.Percentiles("open_percs")
	if !ok {
		return thresholds, err
	}
	var merticsMap = map[int64]int64{}
	for k, v := range openPercMertics.Values {
		kk, _ := strconv.ParseFloat(k, 64)
		merticsMap[int64(kk)] = int64(v)
	}

	for k, v := range thresholds {
		thresholds[k].Start = merticsMap[v.Start]
		if v.End != -1 {
			thresholds[k].End = merticsMap[v.End]
		}
	}
	return thresholds, nil
}

type Threshold struct {
	Start int64 `json:"start"`
	End   int64 `json:"end"`
	Score int64 `json:"score"`
}

type postRules struct {
}

// 时效性
//
// 		{
// 		  "query": {
// 		    "function_score": {
// 		      "functions": [
// 		        {
// 		          "script_score": {
// 		            "script": {
// 		              "source": """
// 		           ZonedDateTime nowZdt= ZonedDateTime.parse(params['now']);
// 		            ZonedDateTime createdZdt = doc['created'].value;
// 		            long diffDays = ChronoUnit.DAYS.between(createdZdt,nowZdt);
// 		            ArrayList  thresholds =params['thresholds'];
// 		            for (int i=0; i<thresholds.length; ++i){
// 		              if (diffDays>=thresholds[i]['start']){
// 		                if (thresholds[i]['end']==-1||diffDays<thresholds[i]['end']){
// 		                  return thresholds[i]['score']+diffDays;
// 		                }
// 		              }
// 		            }
// 		            return 1;
// 		            """,
// 		              "params": {
// 		                "now": "2020-07-07T14:11:58.6787537+08:00",
// 		                "thresholds": [
// 		                  {
// 		                    "start": 0,
// 		                    "end": 2,
// 		                    "score": 90
// 		                  },
// 		                  {
// 		                    "start": 2,
// 		                    "end": 3,
// 		                    "score": 80
// 		                  },
// 		                  {
// 		                    "start": 3,
// 		                    "end": -1,
// 		                    "score": 70
// 		                  }
// 		                ]
// 		              }
// 		            }
// 		          }
// 		        }
// 		      ]
// 		    }
// 		  }
// 		}
//
func (r postRules) TimeAging(thresholds []Threshold, scale float64) *elastic.ScriptFunction {
	var params = map[string]interface{}{
		"thresholds": thresholds,
		"now":        time.Now().Format(time.RFC3339),
		"scale":      scale,
	}
	script := elastic.NewScript(`
       ZonedDateTime nowZdt= ZonedDateTime.parse(params['now']);
        ZonedDateTime createdZdt = doc['created'].value;
        long diffDays = ChronoUnit.DAYS.between(createdZdt,nowZdt);
        ArrayList  thresholds =params['thresholds'];
        for (int i=0; i<thresholds.length; ++i){
          if (diffDays>=thresholds[i]['start']){
            if (thresholds[i]['end']==-1||diffDays<thresholds[i]['end']){
              return thresholds[i]['score']*params.scale;
            }
          }
        }
        return params.scale;
	`).Params(params)
	return elastic.NewScriptFunction(script)
}

// 距离评分
func (r postRules) Distance(latLong string, thresholds []Threshold, scale float64) *elastic.ScriptFunction {
	var params = map[string]interface{}{
		"thresholds": thresholds,
		"origin":     latLong,
		"scale":      "50km",
		"offset":     "0km",
		"decay":      0.5,
		"self_scale": scale,
	}
	script := elastic.NewScript(`
	  if(doc['location'].size()==0 || params.origin.length()==0){return params.self_scale;}
      double distance =  100*decayGeoLinear(params.origin,params.scale, params.offset,params.decay,doc['location'].value);
      ArrayList  thresholds =params['thresholds'];
      for (int i=0; i<thresholds.length; ++i){
        if (distance>=thresholds[i]['start']){
          if (thresholds[i]['end']==-1||distance<thresholds[i]['end']){
            return thresholds[i]['score']*params.self_scale
          }
        }
      }
      return params.self_scale;
	`).Params(params)
	return elastic.NewScriptFunction(script)
}

// 相关度评分part1. 兴趣评分
func (r postRules) RelationInterest(readCount int64, thresholds []Threshold, scale float64) *elastic.ScriptFunction {
	var score = scale
	for _, t := range thresholds {
		if readCount >= t.Start {
			if t.End == -1 || readCount < t.End {
				score = float64(t.Score) * scale
				break
			}
		}
	}
	// default
	return elastic.NewScriptFunction(elastic.NewScript(`return params.score`).Param("score", score))
}

// 相关度评分part2. 用户资料评分
func (r postRules) RelationPropertity(scale float64, thresholds []Threshold) float64 {
	return 100 * scale
}

type QualityInteactParam struct {
	Shares          float64 `json:"shares"`           // 分享次数比分
	Likes           float64 `json:"likes"`            // 点赞次数比分
	Comments        float64 `json:"comments"`         // 评论次数比分
	PrivateMessages float64 `json:"private_messages"` // 私信次数比分
}

// 质量评分part1. 互动
// TODO 貌似要针对频道进行定制, 需要根据不同频道选择不同的权重
func (r postRules) QualityInteract2(paramsStruct map[string]QualityInteactParam, scale float64) *elastic.ScriptFunction {
	var params = map[string]interface{}{}
	_ = mapstructure.Decode(paramsStruct, &params)
	params["scale"] = scale
	script := elastic.NewScript(`
	Map rates = params[doc['channel'].value];
	if (rates ==null){return params.scale}
	double score = doc['shares'].value*rates.shares+
		doc['likes'].value*rates.likes+
		doc['comments'].value*rates.comments+
		doc['private_messages'].value*rates.private_messages;
	return score*params.scale;
	`).Params(params)
	return elastic.NewScriptFunction(script)
}

func (r postRules) QualityInteract(field string, thresholds map[string][]Threshold, cScale map[string]float64, globalThresholds []Threshold, gScale float64) *elastic.ScriptFunction {
	var params = map[string]interface{}{}
	params["thresholds"] = thresholds
	params["global_thresholds"] = globalThresholds
	params["c_scale"] = cScale
	params["g_scale"] = gScale
	var scritpString = fmt.Sprintf(`
ArrayList thresholds = new ArrayList();
double scale = 0;
String channel = doc['channel'].value;
thresholds =params['thresholds'][channel];
if (thresholds ==null){
    thresholds =params['global_thresholds'];
    scale=scale * params['g_scale'];
}else{
    scale=params['c_scale'][doc['channel'].value];
}
long v = doc['%s'].value;
for (int i=0; i<thresholds.length; ++i){
if (v>=thresholds[i]['start']){
  if (thresholds[i]['end']==-1||v<thresholds[i]['end']){
    return thresholds[i]['score']*scale
  }
}
}
return 0;
	`, field)
	script := elastic.NewScript(scritpString).Params(params)
	return elastic.NewScriptFunction(script)
}

// 质量评分part2.1 浏览-打开
func (r postRules) QualityViewOpen(thresholds []Threshold, scale float64) *elastic.ScriptFunction {
	var params = map[string]interface{}{}
	params["thresholds"] = thresholds
	params["scale"] = scale
	script := elastic.NewScript(`
	 long opens = doc['opens'].value;
      ArrayList  thresholds =params['thresholds'];
      for (int i=0; i<thresholds.length; ++i){
        if (opens>=thresholds[i]['start']){
          if (thresholds[i]['end']==-1||opens<thresholds[i]['end']){
            return thresholds[i]['score']
          }
        }
      }
      return 1;
	`).Params(params)
	return elastic.NewScriptFunction(script)
}

// 质量评分part2.2 浏览-有效浏览
func (r postRules) QualityViewPartReads(thresholds []Threshold, scale float64) *elastic.ScriptFunction {
	var params = map[string]interface{}{}
	params["thresholds"] = thresholds
	params["scale"] = scale
	script := elastic.NewScript(`
	if (doc['opens'].value==0)return 0;
	 long part_reads_rate = doc['part_reads'].value/doc['opens'].value;
      ArrayList  thresholds =params['thresholds'];
      for (int i=0; i<thresholds.length; ++i){
        if (part_reads_rate>=thresholds[i]['start']){
          if (thresholds[i]['end']==-1||part_reads_rate<thresholds[i]['end']){
            return thresholds[i]['score']
          }
        }
      }
      return 1;
	`).Params(params)
	return elastic.NewScriptFunction(script)
}

// 质量评分part2.2 浏览-完读
func (r postRules) QualityViewAllReads(thresholds []Threshold, scale float64) *elastic.ScriptFunction {
	var params = map[string]interface{}{}
	params["thresholds"] = thresholds
	params["scale"] = scale
	script := elastic.NewScript(`
	if (doc['opens'].value==0)return 0;
	 long all_reads_rate = doc['all_reads'].value/doc['opens'].value;
      ArrayList  thresholds =params['thresholds'];
      for (int i=0; i<thresholds.length; ++i){
        if (all_reads_rate>=thresholds[i]['start']){
          if (thresholds[i]['end']==-1||all_reads_rate<thresholds[i]['end']){
            return thresholds[i]['score']
          }
        }
      }
      return 1;
	`).Params(params)
	return elastic.NewScriptFunction(script)
}

// 质量评分part2.2 浏览-完读
func (r postRules) HumanScore() *elastic.ScriptFunction {
	script := elastic.NewScript(`
	return doc['recommend_score'].value;
	`)
	return elastic.NewScriptFunction(script)
}

type posts struct {
	db *xorm.Engine
}

func (p posts) GetLikedPostIdsBatch(userId int64, postIds ...int64) (map[int64]interface{}, error) {
	return NewLikes().GetLikeStatus(userId, model.LikesType_Post, postIds...)
}

func (p posts) GetByGroupId(groupId int64, location string, pagination requests.Pagination) ([]PostsWithDistance, error) {
	panic("implement me")
}

func (p posts) Belong(userId int64, id int64) bool {
	var m = model.Posts{}
	found, err := p.db.ID(id).Where("user_id=?", userId).Where("see_status=?", model.Post_See_EveryBody).Get(&m)
	if err != nil || !found {
		return false
	}
	return true
}

func (p posts) UpdateGroup(userId int64, groupIds ...int64) error {
	m := model.Posts{
		RelatedGroups: groupIds,
	}
	_, err := p.db.Where("user_id=?", userId).Update(&m)
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return nil
}

// Deprecated
func (p posts) ListRecommend(user model.Users, pagination requests.GetRecommendPosts, groupIds ...interface{}) ([]model.Posts, error) {
	/**
	select * from posts
		join groups_users on posts.userId= groups_users.userId
		where groups_users.group_id in (groupIds)
	*/
	var ms []model.Posts
	var err error
	err = p.db.In("groups_users.group_id", groupIds...).
		Join("join", "groups_users", "groups_users.userId=posts.userId").
		OrderBy("posts.id desc").
		Find(&ms)
	if err != nil {
		return ms, errcode.NewError(errcode.DaoFailed, err)
	}
	return ms, nil
}

func (p posts) GetById(id int64) (model.Posts, error) {
	var m = model.Posts{}
	found, err := p.db.ID(id).Where("see_status=?", model.Post_See_EveryBody).Get(&m)
	if err != nil {
		return m, errcode.NewError(errcode.DaoFailed, err)
	}
	if !found {
		return m, errcode.NewError(errcode.RequestParamInvalid)
	}
	return m, nil
}

func (p posts) List(userId int64, pagination requests.Pagination) ([]model.Posts, error) {
	var rows = []model.Posts{}
	err := p.db.Where("user_id=?", userId).Where("see_status=?", model.Post_See_EveryBody).Find(&rows)
	if err != nil {
		return rows, errcode.NewError(errcode.DaoFailed, err)
	}
	return rows, err
}
func (p posts) IdList(userId int64) ([]int64, error) {
	var rows = []model.Posts{}
	var ids []int64
	err := p.db.Cols("id").Where("user_id=?", userId).Find(&rows)
	if err != nil {
		return ids, errcode.NewError(errcode.DaoFailed, err)
	}
	for _, row := range rows {
		ids = append(ids, row.Id)
	}
	return ids, nil
}

func (p posts) AddStat(id int64, statType model.PostStatType) error {
	_, err := p.db.Id(id).Incr(string(statType), 1).Update(&model.Posts{})
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return nil
}
func (p posts) SubStat(id int64, statType model.PostStatType) error {
	_, err := p.db.Id(id).Decr(string(statType), 1).Update(&model.Posts{})
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return nil
}

func (p posts) GetByIds(ids ...int64) (map[int64]model.Posts, error) {
	var ps = []model.Posts{}
	var psMap = map[int64]model.Posts{}

	err := p.db.In("id", ids).
		Where("see_status=?", model.Post_See_EveryBody).Find(&ps)
	if err != nil {
		return psMap, errcode.NewError(errcode.DaoFailed, err)
	}

	for _, pp := range ps {
		psMap[pp.Id] = pp
	}
	return psMap, err
}

func (p posts) Insert(userId int64, mm model.PostsModifiable) (model.Posts, error) {
	m := model.Posts{
		UId:             model.UId{userId},
		PostsModifiable: mm,
		SeeStatus:       model.Post_See_EveryBody,
	}
	userGroups, err := NewGroupsUsersDao().GetListByUserId(userId)
	if err != nil {
		return m, err
	}
	var gids []int64
	for _, g := range *userGroups {
		gids = append(gids, g.Id)
	}
	m.RelatedGroups = gids
	_, err = p.db.InsertOne(&m)
	if err != nil {
		return m, errcode.NewError(errcode.DaoFailed, err)
	}
	return m, err
}

func (p posts) Hide(userId int64, id int64) error {
	var m = model.Posts{
		SeeStatus: model.Post_See_Hide,
	}
	_, err := p.db.Where("id=?", id).Where("user_id=?", userId).Update(m)
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return err
}

func (p posts) Update(id int64, m model.PostsModifiable) error {
	toUpdate := model.Posts{
		PostsModifiable: m,
	}
	_, err := p.db.Where("id=?", id).Update(toUpdate)
	if err != nil {
		return errcode.NewError(errcode.DaoFailed, err)
	}
	return err
}
